
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>
using namespace boost;
namespace po = boost::program_options;

#include <xbase_utility.hpp>
using namespace xbase;

class mapped_file_memory
{
public:
	mapped_file_memory(size_t pagesize)
		: m_pagesize(pagesize)
		, m_fd(-1)
		, m_start(NULL)
		, m_size(0) {}

	~mapped_file_memory() {
		close(m_fd);
		munmap(m_start, m_size);
	}

	int mmap(const char *filename, size_t size) {
		int fd = open(filename,
			      O_RDWR | O_CREAT | O_TRUNC,
			      S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
		if(fd < 0)
		{
			char buf[1024];
			printf("open file %s failed: %s\n",
			       filename,
			       strerror_r(errno, buf, sizeof(buf)));
			return -1;
		}

		if(ftruncate(fd, size) < 0)
		{
			char buf[1024];
			printf("ftruncate file %s failed: %s\n",
			       filename,
			       strerror_r(errno, buf, sizeof(buf)));
			return -1;
		}

		char *src = (char*)::mmap(NULL, size, PROT_READ | PROT_WRITE,
					  MAP_SHARED, fd, 0);
		if(src == MAP_FAILED)
		{
			char buf[1024];
			printf("mmap file %s failed: %s\n",
			       filename,
			       strerror_r(errno, buf, sizeof(buf)));
			return -1;
		}

		// init
		m_fd = fd;
		m_start = src;
		m_size = size;

		return 0;
	}

	int non_mmap(const char *filename, size_t size) {
		int fd = open(filename,
			      O_RDWR | O_CREAT | O_TRUNC,
			      S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
		if(fd < 0)
		{
			char buf[1024];
			printf("open file %s failed: %s\n",
			       filename,
			       strerror_r(errno, buf, sizeof(buf)));
			return -1;
		}

		// init
		m_fd = fd;
		m_start = NULL;
		m_size = size;

		return 0;
	}

	char *fill(const char *data, size_t offset, size_t size) {
		if(offset + size > m_size)
		{
			printf("out of bound\n");
			return NULL;
		}
		if(m_start == NULL)
		{
			printf("mmap needed\n");
			return NULL;
		}

		char *start = m_start + offset;
		memcpy(start, data, size);
		return start;
	}

	char *fill_with_flush_all(const char *data, size_t offset, size_t size) {
		char *start = fill(data, offset, size);
		if(start != NULL)
			flush();
		return start;
	}

	char *fill_with_flush_all_async(const char *data, size_t offset, size_t size) {
		char *start = fill(data, offset, size);
		if(start != NULL)
			flush_async();
		return start;
	}

	char *fill_with_flush_region(const char *data, size_t offset, size_t size) {
		char * start = fill(data, offset, size);
		if(start != NULL)
		{
			char *flush_start = adjust_ptr(start);
			size_t flush_size = adjust_size(start, size);
			flush(flush_start, flush_size);
		}
		return start;
	}
	
	char *fill_with_flush_region_async(const char *data, size_t offset, size_t size) {
		char *start = fill(data, offset, size);
		if(start != NULL)
		{
			char *flush_start = adjust_ptr(start);
			size_t flush_size = adjust_size(start, size);
			flush_async(flush_start, flush_size);
		}
		return start;
	}

	char *fill_non_map(const char *data, size_t offset, size_t size) {
		if(offset + size > m_size)
		{
			printf("out of bound\n");
			return NULL;
		}
		if(m_start != NULL)
		{
			printf("mmap not needed\n");
			return NULL;
		}
		ssize_t ret = write(m_fd, data, size);
		if(ret != (ssize_t)size)
		{
			char buffer[1024];
			printf("write file error: %s\n", strerror_r(errno, buffer, sizeof(buffer)));
			return NULL;
		}
		return (char*)(-1);
	}

	char *fill_non_map_with_flush(const char *data, size_t offset, size_t size) {
		char *start = fill_non_map(data, offset, size);
		if(start != NULL)
		{
			if(fsync(m_fd) < 0)
			{
				char buffer[1024];
				printf("fsync file error: %s\n", strerror_r(errno, buffer, sizeof(buffer)));
			}
		}
		return start;
	}

protected:
	static
	char *adjust_ptr(char *data) {
		const unsigned long mask = 0x0FFFUL;
		return (char*)((unsigned long)(data) & (~ mask));
	}

	static
	size_t adjust_size(char *data, size_t size) {
		const unsigned long mask = 0x0FFFUL;
		return size + (size_t)((unsigned long)(data) & mask);
	}

	void flush() {
		flush(m_start, m_size);
	}

	void flush_async() {
		flush_async(m_start, m_size);
	}

	static
	void flush(char *start, size_t size) {
		flush_by(start, size, MS_SYNC);
	}
	
	static
	void flush_async(char *start, size_t size) {
		flush_by(start, size, MS_ASYNC);
	}

	static
	void flush_by(char *start, size_t size, int mode) {
		if(msync(start, size, mode) < 0)
		{
			char buf[1024];
			printf("msync failed: %s\n",
			       strerror_r(errno, buf, sizeof(buf)));
		}
	}
	
private:
	size_t m_pagesize;
	int m_fd;
	char *m_start;
	size_t m_size;
};

typedef int (mapped_file_memory::*map_func_t)(const char *filename, size_t size);
typedef char *(mapped_file_memory::*fill_func_t)(const char *data, size_t offset, size_t size);

const char *test_data_tiny = "mmap_performance_test\n";

const char *test_data_small = "this is my test data for mmap_performance_test, do you like it?\n";

const char *test_data_big =
	"here is some news:"
	"\n"
	"iandoh writes \"Stanford researchers have found that voters are subconsciously swayed by candidates who share their facial features. In three experiments, researchers at the Virtual Human Interaction Lab worked with cheap, easy-to-use computer software to morph pictures of about 600 test subjects with photos of politicians. And they kept coming up with the same results: For the would-be voters who weren't very familiar with the candidates or in perfect lockstep with their positions or political parties, the facial similarity was enough to clinch their votes.\""
	"\n"
	"notthatwillsmith writes \"Earlier this month, the most technologically-advanced digital planetarium in the world opened in San Francisco's California Academy of Sciences. The new Morrison Planetarium's 75-foot screen replaces the traditional Zeiss projector with an array of 6 high-resolution DLP projectors arrayed around the edge of the theater, which are powered by three very different, but interesting computing clusters. The three clusters allow for projection of traditional planetarium shows, playback of ultra-high resolution movies, and display of anything from current atmospheric conditions on Earth to a (greatly accelerated) trip to the farthest reaches of the universe, all rendered in real-time on an 8800 sq. ft. dome. Maximum PC went on a behind the scenes tour with the engineers who built the systems that do everything from run the planetarium lights to the sound systems to the tech behind the screen to show you how it works and what it's like to drive, well... the universe.\""
	"\n"
	"CWmike writes \"Ever picked up a cold, frosty beer on a hot summer's day and thought that it simply couldn't get any better? Well, think again. A team of researchers at Rice University in Houston is working on helping Joe Six Pack fight aging and cancer with every swill of beer.\" Thank you science! Now we just need cigarettes that cure baldness."
	"\n\n";

void run_test(const char *filename, size_t size,
	      const char *test_data,
	      map_func_t map_func,
	      fill_func_t fill_func,
	      const char *mode_string,
	      bool enable_validation)
{
	const size_t pagesize = getpagesize();

	const char *data = test_data;
	const size_t fill_size = strlen(test_data);

	const uint64_t start_time = cur_micro_seconds();

	mapped_file_memory m(pagesize);

	if((m.*map_func)(filename, size) < 0)
	{
		return;
	}

	size_t offset = 0;
	size_t fill_count = 0;
	for(; offset < size; )
	{
		const size_t fsize = std::min(size - offset, fill_size);

		const char *start = (m.*fill_func)(data, offset, fsize);
		if(start == NULL)
		{
			printf("fill error: mode is %s, count = %d\n",
			       mode_string, fill_count);
			return;
		}
		// valid
		if(enable_validation && start != (char*)-1)
		{
			if(0 != memcmp(start, data, fsize))
			{
				printf("memcmp failed, count = %d\n",
				       fill_count);
				return;
			}
		}
		offset += fsize;
		++fill_count;
	}

	const uint64_t end_time = cur_micro_seconds();
	const uint64_t used_time = (end_time - start_time);

	// ===stat info===
	uint64_t brev_size = 0;
	std::string size_unit;
	calc_brev_unit<uint64_t>(brev_size, size_unit, size);
	
	const double used_s = ((double)used_time) / (1000 * 1000);

	const double Bps = ((double)size) * 1000 * 1000 / used_time;
	double brev_Bps = 0;
	std::string Bps_unit;
	calc_brev_unit<double>(brev_Bps, Bps_unit, Bps);
	
	cout << "mode: " << mode_string << endl
	     << "size: " << brev_size << " " << size_unit << "B (" << size << ")" << endl
	     << "filename: " << filename << endl
	     << "validation: " << enable_validation << endl
	     << "fill count: " << fill_count << endl
	     << "used time: " << used_s << " s" << endl
	     << "writing speed: " << brev_Bps << " " << Bps_unit << "Bps" << endl
	     << endl;
}

const char *version_info =
	"mmap_performance_test - test performance for read/write file with mmap\n"
	"\n"
	"\tversion 0.0.3\n"
	"\n";

// 对应mode值
const char * range_string = "[0, 7)";

inline bool in_range(int mode) { return (mode >= 0) && (mode < 7); }

const char *suffix[] = { ".0", ".1", ".2", ".3", ".4", ".5", ".6" };

map_func_t map_func_map[] =
{
	&mapped_file_memory::mmap,
	&mapped_file_memory::mmap,
	&mapped_file_memory::mmap,
	&mapped_file_memory::mmap,
	&mapped_file_memory::mmap,
	&mapped_file_memory::non_mmap,
	&mapped_file_memory::non_mmap
};

fill_func_t fill_func_map[] =
{
	&mapped_file_memory::fill,
	&mapped_file_memory::fill_with_flush_all,
	&mapped_file_memory::fill_with_flush_all_async,
	&mapped_file_memory::fill_with_flush_region,
	&mapped_file_memory::fill_with_flush_region_async,
	&mapped_file_memory::fill_non_map,
	&mapped_file_memory::fill_non_map_with_flush
};

const char *mode_string_map[] =
{
	"mapped_file_memory::fill",
	"mapped_file_memory::fill_with_flush_all",
	"mapped_file_memory::fill_with_flush_all_async",
	"mapped_file_memory::fill_with_flush_region",
	"mapped_file_memory::fill_with_flush_region_async",
	"mapped_file_memory::fill_non_map",
	"mapped_file_memory::fill_non_map_with_flush"
};

int main(int argc, char **argv)
{
	const std::string default_filename("/tmp/mmap_performance_test");
	const size_t default_size = 1024 * 1024;
	std::vector<int> default_mode_list;
	for(int i=0; i<7; ++i)
		default_mode_list.push_back(i);
	const std::string default_test_data_type = "tiny";
	const bool default_validation = true;
	
	std::string filename;
	size_t size;
	std::vector<int> mode_list;
	std::string test_data_type;
	bool validation;

	po::options_description desc("options");
	desc.add_options()
		("help,h", "show this message")
		("version", "show version info")
		("filename,f", po::value<std::string>(&filename)->default_value(default_filename),
		 "output data filename, mode string appended")
		("size,s", po::value<size_t>(&size)->default_value(default_size),
		 "mapped file size")
		("test_data_type,t", po::value<std::string>(&test_data_type)->default_value(default_test_data_type),
		 "test data type: tiny, small, big")
		("validation,v", po::value<bool>(&validation)->default_value(default_validation),
		 "enable validation")
		("mode,m", po::value< std::vector<int> >(),
		 "flush mode:\n"
		 " 0 - fill without flushing,\n"
		 " 1 - fill with flushing all,\n"
		 " 2 - fill with flushing all async,\n"
		 " 3 - fill with flushing region,\n"
		 " 4 - fill with flushing region async,\n"
		 " 5 - fill non-map without flushing,\n"
		 " 6 - fill non-map with flushing,\n"
		 "default to all of above")
		;

	po::positional_options_description p;
        p.add("mode", -1);
	
	po::variables_map vm;
	po::store(po::command_line_parser(argc, argv).
		  options(desc).positional(p).run(), vm);
	po::notify(vm);

	if(argc == 1 || vm.count("help"))
	{
		cout << version_info
		     << desc;
		return 0;
	}

	if(vm.count("version"))
	{
		cout << version_info;
		return 0;
	}

	const char *test_data = NULL;
	if(test_data_type == "tiny")
		test_data = test_data_tiny;
	else if(test_data_type == "small")
		test_data = test_data_small;
	else if(test_data_type == "big")
		test_data = test_data_big;

	if(test_data == NULL)
	{
		cout << "test data type is wrong: " << test_data_type.c_str() << endl
		     << version_info
		     << desc;
		return 1;
	}

	if(vm.count("mode"))
	{
		mode_list = vm["mode"].as< std::vector<int> >();
	}
	else
	{
		mode_list = default_mode_list;
	}

	for(size_t i=0; i<mode_list.size(); ++i)
	{
		int mode = mode_list[i];
		if(in_range(mode))
		{
			const std::string fn = filename + suffix[ mode ];
			run_test(fn.c_str(), size, test_data,
				 map_func_map[mode],
				 fill_func_map[mode],
				 mode_string_map[mode],
				 validation);
		}
		else
		{
			printf("mode %d out of range %s\n",
			       mode, range_string);
		}
	}
	
	return 0;
}

// 
// 经测试：
// mode#0是速度最快的，建议使用；
// 至于它可能丢失数据的问题，交由操作系统处理（测试中没有出现过这个问题）。
// 

